const fs = require("fs");
const os = require("os");
const path = require("path");
const vscode = require("vscode");
const exec = require("child_process").exec;
const iconv = require("iconv-lite");

const Util = {
    /**
     * 获取当前所在工程根目录，有3种使用方法：<br>
     *
     * getProjectPath(uri) uri 表示工程内某个文件的路径<br>
     * getProjectPath(document) document 表示当前被打开的文件document对象<br>
     * getProjectPath() 会自动从 activeTextEditor 拿document对象，如果没有拿到则报错
     * @param {any} document
     */
    getProjectPath(document) {
        if (!document) { document = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document : null; }
        if (!document) {
            this.showError("当前激活的编辑器不是文件或者没有文件被打开！");
            return "";
        }
        const currentFile = (document.uri ? document.uri : document).fsPath;
        let projectPath = null;

        let workspaceFolders = vscode.workspace.workspaceFolders.map((item) => item.uri.fsPath);
        // 由于存在Multi-root工作区，暂时没有特别好的判断方法，先这样粗暴判断
        // 如果发现只有一个根文件夹，读取其子文件夹作为 workspaceFolders
        if (workspaceFolders.length === 1 && workspaceFolders[0] === vscode.workspace.rootPath) {
            const rootPath = workspaceFolders[0];
            let files = fs.readdirSync(rootPath);
            workspaceFolders = files.filter((name) => !/^\./g.test(name)).map((name) => path.resolve(rootPath, name));
            // vscode.workspace.rootPath会不准确，且已过时
            // return vscode.workspace.rootPath + '/' + this._getProjectName(vscode, document);
        }
        workspaceFolders.forEach((folder) => { if (currentFile.indexOf(folder) === 0) { projectPath = folder; } });
        if (!projectPath) {
            this.showError("获取工程根路径异常！");
            return "";
        }
        return projectPath;
    },
    /**
     * 获取当前工程名称
     *
     * @param {String} projectPath 当前工程路径
     * @return {String} 当前工程名称
     */
    getProjectName: function(projectPath) { return path.basename(projectPath); },
    getPluginPath() {},
    /**
     * 将一个单词首字母大写并返回
     *
     * @param {String} word 某个字符串
     * @returns {String} 首字母大写的字符
     */
    upperFirstLetter: function(word) { return (word || "").replace(/^\w/, (m) => m.toUpperCase()); },
    /**
     * 将一个单词首字母转小写并返回
     *
     * @param {String} word 某个字符串
     * @returns {String} 首字母小写的字符
     */
    lowerFirstLeter: function(word) { return (word || "").replace(/^\w/, (m) => m.toLowerCase()); },
    /**
     * 全局日志开关，发布时可以注释掉日志输出
     *
     * @param {any} args 日志参数
     */
    log: function(...args) { console.log(...args); },
    /**
     * 全局日志开关，发布时可以注释掉日志输出
     *
     * @param {any} args 日志参数
     */
    error: function(...args) { console.error(...args); },
    /**
     * 弹出错误信息
     *
     * @param {String} info 信息内容
     */
    showError: function(info) { vscode.window.showErrorMessage(info); },
    /**
     * 弹出提示信息
     *
     * @param {String} info 信息内容
     */
    showInfo: function(info) { vscode.window.showInformationMessage(info); },
    /**
     * @param {any} folderPath
     * @param {any} str
     */
    // eslint-disable-next-line no-unused-vars
    findStrInFolder: function(folderPath, str) {},
    /**
     * 从某个文件里面查找某个字符串，返回第一个匹配处的行与列，未找到返回第一行第一列
     *
     * @param {String} filePath 要查找的文件路径
     * @param {string | RegExp} reg 正则对象，最好不要带g，也可以是字符串
     * @returns {Object} {行号, 列号}
     */
    function(filePath, reg) {
        const content = fs.readFileSync(filePath, "utf-8");
        reg = typeof reg === "string" ? new RegExp(reg, "m") : reg;
        // 没找到直接返回
        if (content.search(reg) < 0) { return { row: 0, col: 0 }; }
        const rows = content.split(os.EOL);
        // 分行查找只为了拿到行
        for (let i = 0; i < rows.length; i++) {
            let col = rows[i].search(reg);
            if (col >= 0) { return { row: i, col }; }
        }
        return { row: 0, col: 0 };
    },
    /**
     * 获取某个字符串在文件里第一次出现位置的范围
     *
     * @param {String} filePath 要查找的文件路径
     * @param {String} str 要查找的字符
     */
    getStrRangeInFile: function(filePath, str) {
        let pos = this.findStrInFile(filePath, str);
        return new vscode.Range(
            new vscode.Position(pos.row, pos.col),
            new vscode.Position(pos.row, pos.col + str.length)
        );
    },
    /**
     * 简单的检测版本大小
     *
     * @param {any} version1 第一个版本
     * @param {any} version2 第二个版本
     * @returns {Boolean} 第一个版本是否大于第二个版本
     */
    checkVersion: function(version1, version2) { return parseInt(version1.replace(/\./g, "")) > parseInt(version2.replace(/\./g, "")); },
    /**
     * 获取某个扩展文件绝对路径
     *
     * @param {{ extensionPath: string; }} context 上下文
     * @param {String} relativePath 扩展中某个文件相对于根目录的路径，如 images/test.jpg
     * @returns {String} 扩展文件的绝对路径
     */
    getExtensionFileAbsolutePath: function(context, relativePath) { return path.join(context.extensionPath, relativePath); },
    /**
     * 获取某个扩展文件相对于webview需要的一种特殊路径格式
     * 形如：vscode-resource:/Users/toonces/projects/vscode-cat-coding/media/cat.gif
     *
     * @param {{ extensionPath: string; }} context 上下文
     * @param {String} relativePath 扩展中某个文件相对于根目录的路径，如 images/test.jpg
     * @returns {String} 扩展文件的webview需要的一种特殊路径格式
     */
    getExtensionFileVscodeResource: function(context, relativePath) {
        const diskPath = vscode.Uri.file(path.join(context.extensionPath, relativePath));
        return diskPath.with({ scheme: "vscode-resource" }).toString();
    },
    /**
     * 在Finder中打开某个文件或者路径
     *
     * @param {String} filePath 文件路径
     */
    openFileInFinder: function(filePath) {
        if (!fs.existsSync(filePath)) { this.showError("文件不存在：" + filePath); }
        // 如果是目录，直接打开就好
        if (fs.statSync(filePath).isDirectory()) {
            exec(`open ${filePath}`);
        } else {
            // 如果是文件，要分开处理
            // eslint-disable-next-line no-unused-vars
            const fileName = path.basename(filePath);
            filePath = path.dirname(filePath);
            // 这里有待完善，还不知道如何finder中如何选中文件
            exec(`open ${filePath}`);
        }
    },
    /**
     * 在vscode中打开某个文件
     *
     * @param {String} path 文件路径
     * @param {String} text 可选，如果不为空，则选中第一处匹配的对应文字
     */
    openFileInVscode: function(path, text) {
        let options = {};
        if (text) {
            const selection = this.getStrRangeInFile(path, text);
            options = { selection };
        }
        vscode.window.showTextDocument(vscode.Uri.file(path), options);
    },
    /**
     * 用JD-GUI打开jar包
     *
     * @param {String} jarPath jar包文件路径
     */
    openJarByJdGui: function(jarPath) {
        // 如何选中文件有待完善
        const jdGuiPath = vscode.workspace.getConfiguration().get("eggHelper.jdGuiPath");
        if (!jdGuiPath) {
            this.showError("JD-GUI路径不能为空！");
            return;
        }
        if (!fs.existsSync(jdGuiPath)) {
            this.showError("您还没有安装JD-GUI，请安装完后到vscode设置里面找到HSF助手并进行路径配置。");
            return;
        }
        if (!fs.existsSync(jarPath)) {
            this.showError("jar包未找到：" + jarPath);
            return;
        }
        exec(`open ${jarPath} -a ${jdGuiPath}`);
    },
    /**
     * 使用默认浏览器中打开某个URL
     *
     * @param {String} url 需要打开的URL
     */
    openUrlInBrowser: function(url) { exec(`open '${url}'`); },
    /**
     * 递归遍历清空某个资源的require缓存
     *
     * @param {String} absolutePath 资源绝对路径
     */
    clearRequireCache(absolutePath) {
        const root = require.cache[absolutePath];
        if (!root) { return; }
        // 如果有子依赖项，先清空依赖项的缓存
        if (root.children) { root.children.forEach((item) => { this.clearRequireCache(item.id); }); }
        delete require.cache[absolutePath];
    },
    /**
     * 动态require，和普通require不同的是，加载之前会先尝试删除缓存
     *
     * @param {String} modulePath
     */
    dynamicRequire(modulePath) {
        this.clearRequireCache(modulePath);
        return require(modulePath);
    },
    /**
     * 读取properties文件
     *
     * @param {String} filePath 文件路径
     * @returns {Object} properties文件数据
     */
    readProperties(filePath) {
        const content = fs.readFileSync(filePath, "utf-8");
        let rows = content.split(os.EOL);
        rows = rows.filter((row) => row && row.trim() && !/^#/.test(row));
        const result = {};
        rows.forEach((row) => {
            let temp = row.split("=");
            result[temp[0].trim()] = temp[1].trim();
        });
        return result;
    },
    /**
     * 比较2个对象转JSON字符串后是否完全一样
     * 特别注意，由于JS遍历对象的无序性（部分浏览器是按照添加顺序遍历的）导致同样的对象，
     * 转成JSON串之后反而不一样，所以这里采用其它方式实现。
     *
     * @param {any} obj1
     * @param {any} obj2
     * @returns {Boolean} 两个对象JSON字符串化的比较结果
     */
    jsonEquals(obj1, obj2) { return this.formatToSpecialJSON(obj1, "", true) === this.formatToSpecialJSON(obj2, "", true); },
    /**
     * 判断路径是否为文件夹
     *
     * @param {String} folderPath 文件夹路径
     * @returns {Boolean} 是否为文件夹的判断结果
     */
    isDirectory(folderPath) { if (fs.existsSync(folderPath)) { return fs.statSync(folderPath).isDirectory(); } },
    /**
     * 判断路径是否为文件
     *
     * @param {String} fileName 文件路径
     * @returns {Boolean} 是否为文件的判断结果
     */
    isFile(fileName) { if (fs.existsSync(fileName)) { return fs.statSync(fileName).isFile(); } },
    /**
     * 从文件夹/文件中获取文件信息
     *
     * @param {String} filesPath
     * @param {String} postfix
     * @param {Boolean} intoSubFolder
     * @returns {Array} [{name, path}]
     */
    getFilesInfoList(filesPath, postfix, intoSubFolder) {
        let fileInfoList = [];
        if (!fs.existsSync(filesPath)) { return fileInfoList; }
        if (this.isDirectory(filesPath) && intoSubFolder) {
            let files = fs.readdirSync(filesPath);
            files.forEach((fileName) => {
                if (this.isDirectory(path.join(filesPath, fileName)) && intoSubFolder) {
                    let curFileInfoList = this.getFilesInfoList(path.join(filesPath, fileName), postfix, intoSubFolder);
                    curFileInfoList.forEach((curInfoObj) => { fileInfoList.push({ name: curInfoObj["name"], path: curInfoObj["path"] }); });
                } else if (postfix !== "" && fileName.endsWith(postfix)) {
                    fileInfoList.push({ name: fileName.replace(postfix, ""), path: path.join(filesPath, fileName) });
                }
            });
        } else if (this.isFile(filesPath) && postfix !== "" && filesPath.endsWith(postfix)) {
            fileInfoList.push({ name: filesPath.replace(postfix, "").replace(new RegExp(/(.*\\)?/), ""), path: filesPath });
        }
        return fileInfoList;
    },
    /**
     * 从字符数组本中获取指定开始行到指定字符之间的字符
     * 
     * @param {Array} rowDatas 字符数组
     * @param {Number} startRow 开始行数
     * @param {String} endStr 结束字符
     * @returns {Array} [获取到的字符串, 新行数]
     */
    getStringFormRowToStr(rowDatas, startRow, endStr) {
        let matchStr = "";
        let newRow = 0;
        for (let row = startRow; row < rowDatas.length - 1; row++) {
            if (rowDatas[row].indexOf(endStr) !== -1) {
                matchStr += rowDatas[row].substring(0, rowDatas[row].indexOf(endStr));
                break;
            } else {
                matchStr += rowDatas[row] + "\n";
                newRow = row;
            }
        }
        return [matchStr.replace(/^(\s|\n)+|(\s|\n)+$/g, ""), newRow];
    },
    /**
     * 读取文件
     *
     * @param {String} filePath 文件路径
     * @param {String} encoding 读取编码方式
     * @returns {String} 读取到的长字符
     */
    readFile(filePath, encoding) {
        return iconv.decode(Buffer.from(fs.readFileSync(filePath, { encoding: "binary" }), "binary"), encoding);
    },
    /**
     * 写入文件
     * @param {String} filePath 文件路径
     * @param {any} content 写入文件的内容
     * @param {String} encoding 写入编码方式
     * @return {Array} [是否成功, 失败原因]
     */
    // eslint-disable-next-line no-unused-vars
    writeFile(filePath, content, encoding) {
        // 拼接文本
        let writeStr;
        if (content.constructor === Array) {
            writeStr = content.join("\n");
        } else if (content.constructor === Object) {
            writeStr = JSON.stringify(content);
        } else if (content.constructor === String) {
            writeStr = content;
        } else {
            return [false, `不支持该数据类型的写入：${content.constructor}`];
        }

        // 写入文件
        fs.writeFile(filePath, writeStr, { "flag": "a" }, function(err) {
            if (err) { throw err; }
        });
        return [true, writeStr];
    },
    /**
     * 获取字符在长字符中的坐标
     *
     * @param {String} content 长字符内容
     * @param {RegExp} searchReg 查找字符的正则表达式
     * @param {Number} lastRow 值为-1时表示返回找到的第一个匹配项，否则返回指定行数内的最后一个匹配项
     * @returns {Array} [行号, 列号, 本行字符]
     */
    getStringPosition(content, searchReg, lastRow) {
        let contents = content.split("\n");
        let position = new Array(2);
        let findLastRow = lastRow < 0 ? contents.length - 1 : lastRow;
        for (let row = 0; row < findLastRow; row++) {
            if (new RegExp(searchReg).test(contents[row])) { position = [row, contents[row].search(searchReg), contents[row]]; }
        }
        return position;
    },

    /**
     * 去除首尾空白符号(包括换行)
     *
     * @param {String} conetnt 要去除的字符
     * @returns {String} 去除首尾空白后的字符
     */
    trimString(conetnt) { return conetnt.replace(/^(\s|\n)+|(\s|\n)+$/g, ""); },
};

module.exports = Util;